﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.IO.Pipes;
using System.Runtime.Serialization.Formatters.Binary;

namespace ProjetReseau
{
    class Reseau
    {
        private Dictionary<byte, byte[]> mapConnection; // [cn] => array(source, destination)
        private Dictionary<byte, byte> mapAdrDestination; // [adrDestination] => noConnexion
        private Dictionary<byte, byte[][]> mapFileDattente; // [noConnexion] => byte[p(r)][130] : paquet
        private Dictionary<byte, byte> mapAcquittement; //[noConnexion] => [p(r) reçu de la destination]
        private Dictionary<byte, Thread> mapTemporisateur; // [noConnexion] => [Thread du temporisateur]
        private Dictionary<byte, bool> mapConnecte; //[noConnexion] => Connecté ou non-connecté
        private Dictionary<byte, byte[]> mapDisconnect; // [noConnexion] => paquet de N_DISCONNECT.req
        private byte noConnection;
        private static Semaphore semTransport;
        private static Semaphore semLD;

        public Reseau()
        {
            mapConnection = new Dictionary<byte, byte[]>();
            mapAdrDestination = new Dictionary<byte, byte>();
            mapFileDattente = new Dictionary<byte, byte[][]>();
            mapAcquittement = new Dictionary<byte, byte>();
            mapTemporisateur = new Dictionary<byte, Thread>();
            mapConnecte = new Dictionary<byte, bool>();
            mapDisconnect = new Dictionary<byte, byte[]>();

            semTransport = new Semaphore(1, 1);
            semLD = new Semaphore(1, 1);

            Thread thLireTransport = new Thread(this.lireDeTransport);
            thLireTransport.Start();

            Thread thLireLiaisonDonnees = new Thread(this.lireDeLiaisonDonnees);
            thLireLiaisonDonnees.Start();
        }

        private void lireDeTransport()
        {
            while (true)
            {
                using (NamedPipeServerStream pipeServer =
                new NamedPipeServerStream("transtores", PipeDirection.In))
                {
                    pipeServer.WaitForConnection();
                    
                    BinaryFormatter bf = new BinaryFormatter();

                    Primitive prim = (Primitive)bf.Deserialize(pipeServer);


                    Thread thEcrire = new Thread(this.ecritureDeTransport);

                    thEcrire.Start(prim);

                }
            }
        }

        private void ecritureDeTransport(object _obj)
        {
            semTransport.WaitOne();
            Primitive prim = (Primitive)_obj;
            
            // Refuser connexion si adresse source est un multiple de 27
            if (prim.type == "N_CONNECT.req" && (prim.adresseSource % 27) == 0)
            {
                prim.type = "N_DISCONNECT.req";
                ecrireVersTransport(prim);
            }
            else // Sinon on crée le paquet demander par ET
            {
                creerPaquet(prim);
            }
            semTransport.Release();
        }

        private void ecritureDeLD(object _obj)
        {
            semLD.WaitOne();
            
            Paquet paq = (Paquet)_obj;

            byte[] paquet = paq.bytes;

            Primitive prim;

            switch (paquet[1])
            {
                case 0xF: //Paquet de communication établie
                    prim = new Primitive(mapConnection[paquet[0]][1], "N_CONNECT.conf");
                    mapConnecte[paquet[0]] = true;
                    ecrireVersTransport(prim);
                    break;

                case 0x13: //Paquet d'indication de libération
                    prim = new Primitive(mapConnection[paquet[0]][1], "N_DISCONNECT.ind");
                    break;

                default: //Paquet d'aquittement
                    bool acquitement = (paquet[1] % 16) / 8 == 0 ? true : false; //Acquitement positif ou négatif

                    byte pr = (byte)(paquet[1] / 32); //Portion p(r) du byte

                    if ((acquitement && pr > mapAcquittement[paquet[0]]) || !acquitement)
                        mapAcquittement[paquet[0]] = pr;

                    if (mapAcquittement[paquet[0]] > mapFileDattente[paquet[0]].Length)
                    {
                        ecrireVersLiaisonDonnees(mapDisconnect[paquet[0]]);
                        terminerConnexion(paquet[0]);
                    }
                    else
                    {
                        envoyerPaquet(paquet[0]);
                    }
                    break;
            }

            semLD.Release();
        }

        private void creerPaquet(Primitive _prim)
        {
            byte[] paquet;

            switch (_prim.type)
            {
                // Paquet de connexion de réseau
                case "N_CONNECT.req":
                    byte no_conn = ++noConnection;
                    mapConnection.Add(no_conn, new byte[] { _prim.adresseSource, _prim.adresseDestination });
                    mapAdrDestination.Add(_prim.adresseDestination, no_conn);
                    
                    paquet = new byte[4];
                    paquet[0] = no_conn;    // #connexion
                    paquet[1] = 0x0B;            // type paquet
                    paquet[2] = _prim.adresseSource;      // adresse source
                    paquet[3] = _prim.adresseDestination; // adresse destination

                    mapConnecte.Add(paquet[0], false);
                    mapAcquittement.Add(paquet[0], 1);

                    ecrireVersLiaisonDonnees(paquet);
                    
                    creerTemporisateur(paquet[0]);

                    break;
                // Paquet de transfert de données de réseau
                case "N_DATA.req":
                    
                    paquet = new byte[130];  //Le paquet en question
                    
                    System.Text.ASCIIEncoding encoding= new System.Text.ASCIIEncoding();
                    Byte[] bytes = encoding.GetBytes(_prim.text);  //Array de byte contenant le message

                    int nbPaquets = bytes.Length / 128; //On divise le nombre de bytes par 128 pour savoir le nombre de paquets total qu'on aura

                    if (bytes.Length % 128 != 0) // Si le modulo est différent de zéro ça veut dire qu'il y a un dernier paquet plus petit en plus
                    {
                        nbPaquets++; // On ajoute un paquet
                    }
                    
                    byte[][] paquets = new byte[nbPaquets][]; //Déclaration du tableau de byte qui servira à stocker les paquets
                    
                    for (int paquetCourant = 0; paquetCourant < nbPaquets; paquetCourant++) //On boucle dans les paquets qu'on va ajouter à la map
                    {
                        paquets[paquetCourant] = new byte[130];

                        paquets[paquetCourant][0] = mapAdrDestination[_prim.adresseDestination]; // Ajout du numéro de connexion

                        /*Code pour le type de paquet*/

                        paquets[paquetCourant][1] = 32; // On met le p(r) à 1 puisque c'est le prochain paquet attendu

                        if (mapFileDattente.ContainsKey(paquets[0][0]))
                        {
                            paquets[paquetCourant][1] += (byte)(2 * (paquetCourant + mapFileDattente[paquets[0][0]].Length + 1)); //Incrementer le ps avec la valeur du dernier PS de la file d'attente
                        }
                        else
                        {
                            paquets[paquetCourant][1] += (byte)(2 * (paquetCourant + 1)); //On ajoute le p(s) qui est égal au paquet courant + 1
                        }

                        if (paquetCourant != nbPaquets - 1) //Si ce n'est pas le dernier paquet
                        {
                            paquets[paquetCourant][1] += 16; //On met le M à 1
                        }

                        /*Fin du code pour le type de paquet*/

                        for (int byteCourant = 0; byteCourant < 128; byteCourant++) //On boucle jusqu'à 128 pour remplir chaque paquet
                        {
                            if ((paquetCourant + 1) * (byteCourant + 1) > bytes.Length) //Si nous avons atteint la fin de l'array bytes (s'applique seulement au dernier paquet)
                            {
                                break; //On sort de la boucle pour éviter une erreur d'index out of range
                            }

                            //On ajoute le byte
                            paquets[paquetCourant][byteCourant + 2] = bytes[(paquetCourant + 1) * byteCourant]; // On aditionne 2 à byte courant pour laisser la place pour le byte noConnexion et le byte typePaquet
                        }
                    }

                    if (mapFileDattente.ContainsKey(paquets[0][0]))
                    {
                        byte[][] oldArray = mapFileDattente[paquets[0][0]];
                        byte[][] newArray = new byte[oldArray.Length + paquets.Length][];

                        for (int i = 0; i < oldArray.Length; i++)
                        {
                            newArray[i] = oldArray[i];

                            if (i == oldArray.Length - 1)
                            {
                                newArray[i][1] += 16;
                            }
                        }

                        for (int i = 0; i < paquets.Length; i++)
                        {
                            int j = i + oldArray.Length;

                            newArray[j] = paquets[i];
                        }

                        mapFileDattente[paquets[0][0]] = newArray;
                    }
                    else
                    {
                        mapFileDattente.Add(paquets[0][0], paquets); //Ajout du paquet dans la file d'attente
                    }
                    
                   
                    break;
                // Paquet de libération de connexion de réseau
                case "N_DISCONNECT.req":
                    paquet = new byte[4];
                    paquet[0] = mapAdrDestination[_prim.adresseReponse];  // #connexion
                    paquet[1] = 0x13; // type paquet
                    paquet[2] = mapConnection[mapAdrDestination[_prim.adresseReponse]][0]; // adresse source
                    paquet[3] = _prim.adresseReponse; // adresse destination

                    mapDisconnect.Add(paquet[0], paquet);
                    
                    if (mapFileDattente.ContainsKey(paquet[0]))
                        if (mapAcquittement[paquet[0]] > mapFileDattente[paquet[0]].Length)
                            ecrireVersLiaisonDonnees(paquet);

                    break;
            }

        } // Fin de la fonction créer paquet

        private void terminerConnexion(byte noConn) // Fonction qui termine la connexion
        { 
             byte adresseDest = mapConnection[noConn][1];
             
             mapConnection.Remove(noConn);
             mapAdrDestination.Remove(adresseDest);
             mapFileDattente.Remove(noConn);
             mapAcquittement.Remove(noConn);

             //mapTemporisateur[noConn].Join();
             mapTemporisateur.Remove(noConn);
             mapConnecte.Remove(noConn);
             mapDisconnect.Remove(noConn);
        }

        private void ecrireVersTransport(Primitive prim)
        {

            using (NamedPipeClientStream pipeClient =
            new NamedPipeClientStream(".", "restotrans", PipeDirection.Out))
            {
                
                bool retry = false;
                do
                {
                    try
                    {
                        while (!pipeClient.IsConnected)
                            pipeClient.Connect(500);
                    }
                    catch (IOException e)
                    {
                        retry = true;
                    }
                    catch (TimeoutException e)
                    { 
                    
                    }
                }
                while (retry);

                BinaryFormatter bf = new BinaryFormatter();

                bf.Serialize(pipeClient, prim);



                //pipeClient.WaitForPipeDrain();
                pipeClient.Close();
                pipeClient.Dispose();
               
            }
            //semTransport.Release();
           
        } // Fin de la fonction ecrire vers transport

        private void ecrireVersLiaisonDonnees(byte[] _paquet)
        {
            //semLD.WaitOne();

            using (NamedPipeClientStream pipeClient =
            new NamedPipeClientStream( "." , "restold", PipeDirection.Out))
            {
                bool retry = false;
                do
                {
                    try
                    {
                        while (!pipeClient.IsConnected)
                            pipeClient.Connect(500);
                    }
                    catch (IOException e)
                    {

                        retry = true;
                    }
                    catch (TimeoutException e)
                    {
                        retry = true;
                    }
                }
                while (retry);

                Paquet paq = new Paquet(_paquet);



                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(pipeClient, paq);

                //pipeClient.WaitForPipeDrain();
                pipeClient.Close();
                pipeClient.Dispose();
                
            }

            //semLD.Release();
        } // Fin de la fonction écrire vers liaison de données


        private void lireDeLiaisonDonnees()
        {
            while (true)
            {
                try
                {
                    using (NamedPipeServerStream pipeServer =
                    new NamedPipeServerStream("ldtores", PipeDirection.In))
                    {

                        pipeServer.WaitForConnection();

                        BinaryFormatter bf = new BinaryFormatter();

                        Paquet paq = (Paquet)bf.Deserialize(pipeServer);


                        Thread thEcrire = new Thread(this.ecritureDeLD);

                        thEcrire.Start(paq);

                    }
                }
                catch (IOException e)
                {

                }
            }
        } // Fin de la fonction lire de liaison de données

        private void creerTemporisateur(byte noConn) //Fonction qui démarre un nouveau temporisateur
        {
            Thread thTempo = new Thread(this.startTemporisateur);

            thTempo.Start(noConn);
            //thTempo.Name = noConn + "thTempo";
            mapTemporisateur.Add(noConn, thTempo);
        }

        private void startTemporisateur(object noConn)
        {
            byte connexion = (byte)noConn;

            while (mapConnection.ContainsKey(connexion))
            {
                Thread.Sleep(1000);

                if (mapConnecte.ContainsKey(connexion))
                {
                    if (mapConnecte[connexion])
                    {
                        envoyerPaquet(connexion);
                    }
                    else
                    {
                        // IMPORTANT : CRÉER LE PAQUET D'INDICATION DE LIBÉRATION
                        Primitive prim = new Primitive(mapConnection[connexion][1], "N_DISCONNECT.ind");
                        ecrireVersTransport(prim);

                        terminerConnexion(connexion);
                    }
                }
            }

        } // Fin de la fonction start temporisateur

        private void envoyerPaquet(byte noConn)
        {
            if (mapFileDattente.ContainsKey(noConn))
            {
                if (mapFileDattente[noConn] != null)
                {
                    byte paquetAEnvoyer = (byte)(mapAcquittement[noConn] - 1);

                    if (mapFileDattente[noConn].Length > paquetAEnvoyer) 
                        ecrireVersLiaisonDonnees(mapFileDattente[noConn][paquetAEnvoyer]);
                }
            }
            
        }
    } // Fin de la classe réseau
} // Fin du namespace
